<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="限制脚本单例运行">
<meta name="twitter:description" content="<p>锁文件：把文件系统的文件作为锁，获取到文件权限即获取到锁；flock命令：crontab中加锁，shell脚本中加锁，只能单例执行的shell脚本；自己编写python模块实现加锁。</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/lock-program-by-file.html"/>
<meta property="og:title" content="来玩魔王的咚 | 限制脚本单例运行" />
<meta property="og:description" content="<p>锁文件：把文件系统的文件作为锁，获取到文件权限即获取到锁；flock命令：crontab中加锁，shell脚本中加锁，只能单例执行的shell脚本；自己编写python模块实现加锁。</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>锁文件：把文件系统的文件作为锁，获取到文件权限即获取到锁；flock命令：crontab中加锁，shell脚本中加锁，只能单例执行的shell脚本；自己编写python模块实现加锁。</p>" />

    <meta name="tags" content="shell" />
    <meta name="tags" content="python" />
    <meta name="tags" content="进程锁" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li><a href="/category/go.html">Go</a></li>
                            <li class="active"><a href="/category/linux.html">Linux</a></li>
                            <li><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2020-08-17 11:00:00+08:00">
                <script>document.write(moment('2020-08-17 11:00:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2020-08-17 11:00:00+08:00">
                <script>document.write(moment('2020-08-17 11:00:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            限制脚本单例运行
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#flock">flock</a><ul>
<li><a href="#bang-zhu-shuo-ming">帮助说明</a></li>
<li><a href="#ming-ling-xing-zhong-shi-yong">命令行中使用</a></li>
<li><a href="#jiao-ben-zhong-shi-yong">脚本中使用</a></li>
<li><a href="#dan-li-zhi-xing-jiao-ben">单例执行脚本</a></li>
</ul>
</li>
<li><a href="#python-zhong-de-shi-xian">Python中的实现</a><ul>
<li><a href="#da-kai-wen-jian">打开文件</a></li>
<li><a href="#mo-kuai-dai-ma">模块代码</a></li>
<li><a href="#shi-zheng-ge-python-jiao-ben-bian-cheng-dan-li-atexit-mo-kuai">使整个python脚本变成单例(atexit 模块)</a></li>
</ul>
</li>
</ul>
</div>
<p>有没有遇到过，在运行脚本的时候不小心手抽了一下，结果运行了两遍。或者计划任务超时，结果前一次还没结束，新的一轮又开始了。<br>
这里利用了文件系统，通过获取文件（这个文件就是锁文件）的权限来获取锁。限制同一时间脚本只能被执行一次。  </p>
<h3 id="flock"><a class="toclink" href="#flock">flock</a></h3>
<p>当多个进程可能会执行同一个脚本，这些进程需要保证其它进程没有在操作，以免重复执行。通常，这样的进程会使用一个<strong>锁文件</strong>，也就是建立一个文件来告诉别的进程自己在运行，如果检测到那个文件存在则认为有操作同样数据的进程在工作。<br>
flock通过文件锁的方式来保证独占运行，并且还有一个好处是进程退出时，会自动释放文件，不需要额外的处理。  </p>
<h4 id="bang-zhu-shuo-ming"><a class="toclink" href="#bang-zhu-shuo-ming">帮助说明</a></h4>
<div class="highlight"><pre><span></span><code>$ flock -h

用法：
 flock <span class="o">[</span>选项<span class="o">]</span> &lt;文件<span class="p">|</span>目录&gt; &lt;命令&gt; <span class="o">[</span>命令参数<span class="o">]</span>
 flock <span class="o">[</span>选项<span class="o">]</span> &lt;文件<span class="p">|</span>目录&gt; -c &lt;命令&gt;
 flock <span class="o">[</span>选项<span class="o">]</span> &lt;文件描述符号码&gt;

选项：
 -s  --shared             获得共享锁
 -x, -e  --exclusive      获得排他锁<span class="o">(</span>默认<span class="o">)</span>
 -u  --unlock             移除锁: 通常是不需要的，脚本执行完会自动丢弃锁
 -n  --nonblock           失败而不等待： 如果没有立即获得锁，直接失败而不是等待
 -w  --timeout &lt;秒数&gt;     等待一段时间: 如果没有立即获得锁，等待指定时间
 -E  --conflict-exit-code &lt;数字&gt;    冲突或超时后的退出代码
 -o  --close              在运行命令前关闭文件描述符： 用于如果命令产生子进程时会不受锁的管控
 -c  --command &lt;命令&gt;     通过 shell 运行一条命令

 -h, --help     显示此帮助并退出
 -V, --version  输出版本信息并退出

更多信息请参阅 flock<span class="o">(</span><span class="m">1</span><span class="o">)</span>。
$ 
</code></pre></div>

<p>前两种用法，都是通过文件名（也可以是目录）获取锁，然后执行后面的命令。<br>
第三种用法，尝试获取文件描述符指向的文件。成功则执行成功，退出码是0。否则执行失败，默认失败的退出码是1。  </p>
<h4 id="ming-ling-xing-zhong-shi-yong"><a class="toclink" href="#ming-ling-xing-zhong-shi-yong">命令行中使用</a></h4>
<p>在crontab里很有用，防止多个实例同时运行。这种周期性运行的脚本，在前一次还没运行完时，不希望下一轮再运行：</p>
<div class="highlight"><pre><span></span><code>$ crontab -l
*/5 * * * * flock -w .05 -x filename.lock -c <span class="s1">&#39;/bin/bash myscrit.sh &amp;&gt;/dev/null&#39;</span>
</code></pre></div>

<p>-x 指定使用排它锁，这个是默认的，所以不加应该也行。后面的锁文件名不是-x选项的值，而是一个独立的位置参数。<br>
-w 等待的时间允许使用小数（Decimal fractional values are allowed），最前面的0也省了。<br>
-c 放在最后，表示后面的部分都是命令。这个是用法二。<br>
也可以不用-c，但是后面的命令部分的引号也要去掉。这个是用法一。  </p>
<h4 id="jiao-ben-zhong-shi-yong"><a class="toclink" href="#jiao-ben-zhong-shi-yong">脚本中使用</a></h4>
<p>这里要使用flock的第三种用法。根据获取锁的结果，执行不同的逻辑。<br>
这段是man说明中的示例代码：</p>
<div class="highlight"><pre><span></span><code><span class="o">(</span>
    flock -n <span class="m">9</span> <span class="o">||</span> <span class="nb">exit</span> <span class="m">1</span>
    <span class="c1"># 获取到锁之后需要执行的代码</span>
<span class="o">)</span> <span class="m">9</span>&gt;/var/lock/mylockfile
</code></pre></div>

<p>这里用的是小括号，小括号中的exit仅仅是退出这个子进程。并不会退出脚本。<br>
这里的逻辑是，根据是否能获取到锁，决定小括号中的这些命令是否要执行。  </p>
<p>下面是另一种形式的用法：</p>
<div class="highlight"><pre><span></span><code><span class="nb">exec</span> <span class="m">123</span>&lt;&gt;/var/lock/mylockfile  <span class="c1"># 把 mylockfile 打开为文件描述符 123</span>
flock -w <span class="m">5</span> <span class="m">123</span> <span class="o">||</span> <span class="o">{</span> <span class="nb">echo</span> <span class="s1">&#39;cannot get lock, exit...&#39;</span><span class="p">;</span> <span class="nb">exit</span> <span class="m">1</span><span class="p">;</span> <span class="o">}</span>
</code></pre></div>

<p>如果是大括号组合命令，最后的一条命令后也必须有分号，然后还得空一格再大括号。  </p>
<h5 id="zhong-ding-xiang-fu-hao"><a class="toclink" href="#zhong-ding-xiang-fu-hao">重定向符号</a></h5>
<p>这个指定了打开文件的方式。<br>
这里使用了&gt;，将文件描述符指向了锁文件。用&gt;&gt;也是一样的，因为都是一个空文件。&gt; 和 &gt;&gt; 允许文件不存在，此时会自动创建一个。这个需要有写权限。<br>
也可以使用&lt;，此时要求文件必须存在，不过好处是需要有文件的只读权限就可以了。<br>
当然用&lt;&gt;也是可以的。  </p>
<h5 id="zu-he-ming-ling"><a class="toclink" href="#zu-he-ming-ling">组合命令</a></h5>
<p>这里使用了小括号组合命令。效果是在当前shell中再起一个子shell来执行。<br>
使用大括号应该也是可以的。效果是在当前shell中执行，这种就是代码块。<br>
上面的2段示例代码，都在获取锁失败之后要exit。但是是有差别的：</p>
<ul>
<li>小括号仅仅只是退出这段子进程，相当于if语句里的break。不会退出脚本</li>
<li>大括号里还是当前脚本的进程，于是就是退出脚本运行了</li>
</ul>
<h4 id="dan-li-zhi-xing-jiao-ben"><a class="toclink" href="#dan-li-zhi-xing-jiao-ben">单例执行脚本</a></h4>
<p>效果就和在命令行中使用的效果一样了，直接了脚本中进行限制，只能单例执行。<br>
把下面的内容放到脚本的开头：</p>
<div class="highlight"><pre><span></span><code><span class="o">[</span> <span class="s2">&quot;</span><span class="si">${</span><span class="nv">FLOCKER</span><span class="si">}</span><span class="s2">&quot;</span> !<span class="o">=</span> <span class="s2">&quot;</span><span class="nv">$0</span><span class="s2">&quot;</span> <span class="o">]</span> <span class="o">&amp;&amp;</span> <span class="nb">exec</span> env <span class="nv">FLOCKER</span><span class="o">=</span><span class="s2">&quot;</span><span class="nv">$0</span><span class="s2">&quot;</span> flock -en <span class="s2">&quot;</span><span class="nv">$0</span><span class="s2">&quot;</span> <span class="s2">&quot;</span><span class="nv">$0</span><span class="s2">&quot;</span> <span class="s2">&quot;</span><span class="nv">$@</span><span class="s2">&quot;</span> <span class="o">||</span> :
</code></pre></div>

<p>这段也是man说明文档中直接贴过来的。  </p>
<p>直接用脚本文件自己作为锁文件。<br>
&nbsp;<br>
<strong>第一次执行：</strong><br>
先检查变量FLOCKER不是锁文件名，则继续&amp;&amp;后面的代码。<br>
设置好变量FLOCKER，然后执行flock，尝试获取锁后重新执行自己。这里用了exec，就是将当前进程替换掉，重新用flock的命令执行脚本。<br>
<strong>第二次执行：</strong><br>
这次是用exec替换掉之前的进程重新进入的。由于环境变量设置正确，&amp;&amp;之后的代码被短路了。执行||之后的代码。这里就一个冒号，表示空命令（相当于Python中的pass）。然后就可以继续执行下一行代码了。<br>
&nbsp;<br>
当脚本再次被调用时，此时无法获取到锁。flock就直接退出了。  </p>
<h3 id="python-zhong-de-shi-xian"><a class="toclink" href="#python-zhong-de-shi-xian">Python中的实现</a></h3>
<p>在写python脚本时，有时也希望有类似的功能。这里提供一个纯python的实现方式。<br>
核心思路是利用文件系统，以独占的方式打开(open)一个文件，并且在完成之后再释放掉。  </p>
<h4 id="da-kai-wen-jian"><a class="toclink" href="#da-kai-wen-jian">打开文件</a></h4>
<p>os.open() 方法用于打开一个文件，并且设置需要的打开选项。<br>
用法格式如下：</p>
<div class="highlight"><pre><span></span><code><span class="k">def</span> <span class="nf">open</span><span class="p">(</span><span class="n">path</span><span class="p">,</span> <span class="n">flags</span><span class="p">,</span> <span class="n">mode</span><span class="o">=</span><span class="mi">511</span><span class="p">,</span> <span class="n">dir_fd</span><span class="o">=</span><span class="kc">None</span><span class="p">)</span>
</code></pre></div>

<p>主要参数：</p>
<ul>
<li>path: 要打开的文件</li>
<li>flags: 这个是重点，有以下的权限，可以多选，用"|"按位与设置<ul>
<li>os.O_RDONLY: 以只读的方式打开 </li>
<li>os.O_WRONLY: 以只写的方式打开 </li>
<li>os.O_RDWR : 以读写的方式打开</li>
<li>os.O_NONBLOCK: 打开时不阻塞</li>
<li>os.O_APPEND: 以追加的方式打开</li>
<li>os.O_CREAT: 创建并打开一个新文件</li>
<li>os.O_TRUNC: 打开一个文件并截断它的长度为零（必须有写权限）</li>
<li>os.O_EXCL: 如果指定的文件存在，返回错误</li>
<li>os.O_SHLOCK: 自动获取共享锁</li>
<li>os.O_EXLOCK: 自动获取独立锁</li>
<li>os.O_DIRECT: 消除或减少缓存效果</li>
<li>os.O_FSYNC : 同步写入</li>
<li>os.O_NOFOLLOW: 不追踪软链接</li>
</ul>
</li>
<li>mode: 文件的ugo权限，有默认值，有特别需要在修改，可能不同操作系统默认值也是不同的。</li>
</ul>
<p>返回值: 返回文件的描述符，int类型。</p>
<h5 id="du-zhan-fang-shi-da-kai-wen-jian"><a class="toclink" href="#du-zhan-fang-shi-da-kai-wen-jian">独占方式打开文件</a></h5>
<p>要以独占的方式打开文件，需要用到如下的flags：</p>
<div class="highlight"><pre><span></span><code><span class="n">fd</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="n">lockfile</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">O_CREAT</span><span class="o">|</span><span class="n">os</span><span class="o">.</span><span class="n">O_EXCL</span><span class="o">|</span><span class="n">os</span><span class="o">.</span><span class="n">O_RDWR</span><span class="p">)</span>
</code></pre></div>

<p>os.O_RDWR: 这个似乎没有也没关系（未验证），因为我们不需要对打开的文件进行任何读写操作。<br>
os.O_CREAT|os.O_EXCL: 这2个要一起使用，文件必须不存在。如果文件存在会报错，errno是17。  </p>
<p>这里并没有用到锁，而是通过上面的方式尝试打开一个文件，必须文件不存在。这样就保证了同时只有一个程序可以成功创建并打开文件。<br>
等程序完成后，释放并删除掉锁文件。  </p>
<h4 id="mo-kuai-dai-ma"><a class="toclink" href="#mo-kuai-dai-ma">模块代码</a></h4>
<p>这里以模块的形式写好了代码：</p>
<div class="highlight"><pre><span></span><code><span class="c1"># 文件名: filelock.py</span>
<span class="kn">import</span> <span class="nn">os</span>
<span class="kn">import</span> <span class="nn">time</span>
<span class="kn">import</span> <span class="nn">errno</span>


<span class="k">class</span> <span class="nc">FileLockException</span><span class="p">(</span><span class="ne">Exception</span><span class="p">):</span>
    <span class="k">pass</span>


<span class="k">class</span> <span class="nc">FileLock</span><span class="p">(</span><span class="nb">object</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;通过文件来实现加锁的机制</span>
<span class="sd">    可以通过with关键字来调用。</span>
<span class="sd">    兼容性好，因为只需要文件系统的支持。</span>
<span class="sd">    &quot;&quot;&quot;</span>

    <span class="k">def</span> <span class="fm">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">lockfile</span><span class="p">,</span> <span class="n">timeout</span><span class="o">=</span><span class="mi">10</span><span class="p">,</span> <span class="n">delay</span><span class="o">=</span><span class="mf">.05</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">is_locked</span> <span class="o">=</span> <span class="kc">False</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">lockfile</span> <span class="o">=</span> <span class="n">lockfile</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">timeout</span> <span class="o">=</span> <span class="n">timeout</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">delay</span> <span class="o">=</span> <span class="n">delay</span>

    <span class="k">def</span> <span class="nf">acquire</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="sd">&quot;&quot;&quot;请求锁</span>
<span class="sd">        如果已经被锁定，sleep一段时间(delay)，再次请求。</span>
<span class="sd">        不停尝试直到超时(timeout)，抛出异常。</span>
<span class="sd">        &quot;&quot;&quot;</span>
        <span class="n">start_time</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
        <span class="k">while</span> <span class="kc">True</span><span class="p">:</span>
            <span class="k">try</span><span class="p">:</span>
                <span class="c1">#独占式打开文件</span>
                <span class="bp">self</span><span class="o">.</span><span class="n">fd</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">open</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">lockfile</span><span class="p">,</span> <span class="n">os</span><span class="o">.</span><span class="n">O_CREAT</span><span class="o">|</span><span class="n">os</span><span class="o">.</span><span class="n">O_EXCL</span><span class="o">|</span><span class="n">os</span><span class="o">.</span><span class="n">O_RDWR</span><span class="p">)</span>
                <span class="k">break</span>
            <span class="k">except</span> <span class="ne">OSError</span> <span class="k">as</span> <span class="n">e</span><span class="p">:</span>
                <span class="k">if</span> <span class="n">e</span><span class="o">.</span><span class="n">errno</span> <span class="o">!=</span> <span class="n">errno</span><span class="o">.</span><span class="n">EEXIST</span><span class="p">:</span>
                    <span class="k">raise</span> 
                <span class="k">if</span> <span class="p">(</span><span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span> <span class="o">-</span> <span class="n">start_time</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="bp">self</span><span class="o">.</span><span class="n">timeout</span><span class="p">:</span>
                    <span class="k">raise</span> <span class="n">FileLockException</span><span class="p">(</span><span class="s2">&quot;Timeout occured.&quot;</span><span class="p">)</span>
                <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">delay</span><span class="p">)</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">is_locked</span> <span class="o">=</span> <span class="kc">True</span>

    <span class="k">def</span> <span class="nf">release</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="sd">&quot;&quot;&quot;删除锁文件，释放锁</span>
<span class="sd">        在with语句中，这步会自动被调用执行。</span>
<span class="sd">        &quot;&quot;&quot;</span>
        <span class="c1">#关闭文件，删除文件</span>
        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_locked</span><span class="p">:</span>
            <span class="n">os</span><span class="o">.</span><span class="n">close</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">fd</span><span class="p">)</span>
            <span class="n">os</span><span class="o">.</span><span class="n">unlink</span><span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">lockfile</span><span class="p">)</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">is_locked</span> <span class="o">=</span> <span class="kc">False</span>

    <span class="k">def</span> <span class="fm">__enter__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="k">if</span> <span class="ow">not</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_locked</span><span class="p">:</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">acquire</span><span class="p">()</span>
        <span class="k">return</span> <span class="bp">self</span>

    <span class="k">def</span> <span class="fm">__exit__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="nb">type</span><span class="p">,</span> <span class="n">value</span><span class="p">,</span> <span class="n">traceback</span><span class="p">):</span>
        <span class="k">if</span> <span class="bp">self</span><span class="o">.</span><span class="n">is_locked</span><span class="p">:</span>
            <span class="bp">self</span><span class="o">.</span><span class="n">release</span><span class="p">()</span>

    <span class="k">def</span> <span class="fm">__del__</span><span class="p">(</span><span class="bp">self</span><span class="p">):</span>
        <span class="bp">self</span><span class="o">.</span><span class="n">release</span><span class="p">()</span>

<span class="sd">&quot;&quot;&quot;</span>
<span class="sd"># 调用方式:</span>
<span class="sd">from filelock import FileLock</span>
<span class="sd">with FileLock(&quot;filename.lock&quot;):</span>
<span class="sd">    # 需要在获取锁之后执行的代码块</span>
<span class="sd">    print(&quot;Lock acquired.&quot;)</span>
<span class="sd">&quot;&quot;&quot;</span>
</code></pre></div>

<p>使用with关键字，FileLock类先执行__enter__函数。在__enter__函数中独占创建或打开一个文件，这个文件不会被再次创建或打开，完成加锁。<br>
然后执行with内部的代码块。<br>
执行完成后，在执行__exit__函数，关闭并删除文件。  </p>
<p>使用with的另一个好处是，即使with代码块内有异常，在程序退出前，也会执行执行__exit__函数，删除文件释放锁。<br>
比如下面这样:</p>
<div class="highlight"><pre><span></span><code><span class="k">with</span> <span class="n">FileLock</span><span class="p">(</span><span class="s1">&#39;flock.lock&#39;</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s2">&quot;Get Lock&quot;</span><span class="p">)</span>
    <span class="n">time</span><span class="o">.</span><span class="n">sleep</span><span class="p">(</span><span class="mi">3</span><span class="p">)</span>  <span class="c1"># 这段时间，可以去查看锁文件是被创建出来的</span>
    <span class="mi">1</span><span class="o">/</span><span class="mi">0</span>            <span class="c1"># 抛出异常后再检查锁文件，已经被删除了</span>
</code></pre></div>

<h4 id="shi-zheng-ge-python-jiao-ben-bian-cheng-dan-li-atexit-mo-kuai"><a class="toclink" href="#shi-zheng-ge-python-jiao-ben-bian-cheng-dan-li-atexit-mo-kuai">使整个python脚本变成单例(atexit 模块)</a></h4>
<p>其实，只需要将获取锁的语句放到脚本的开头就好了。问题是如何保证释放锁。<br>
问题一、释放锁的语句应该和获取锁的语句集中放在一起（脚本的开头）。释放锁要在最后执行，这就需要把释放锁的代码放到脚本最后。容易忘记，不方便阅读和维护。<br>
问题二、异常的处理。执行过程会遇到异常，导致程序终止。无法保证后面的代码被执行。  </p>
<p>问题分析：<br>
用with的语法真的很好，但是把整段代码都放到with中也很难看。<br>
还可以写个main函数，把主要的逻辑都放到main函数中。这样可以保证main函数是单例执行的。这是下策，还不够好。程序正式执行main函数前，还有很多其他的代码会执行：定义全局变量、函数，导入模块等，这些一般都放在main函数外。<br>
另外还有装饰器、try，这些也都不是解决办法，还不如上面2条。  </p>
<h5 id="atexit-mo-kuai"><a class="toclink" href="#atexit-mo-kuai">atexit 模块</a></h5>
<p>作用：让注册的函数在解释器正常终止时自动执行，可以注册多个函数，所注册的函数会逆序执行（据查资料，造成逆序的原因为函数压栈造成的，先进后出）。<br>
注意：如果程序是非正常退出，或者通过os._exit()退出，注册的退出函数将不会被调用。<em>应该就是kill -9这种强制终止的情况。</em><br>
这个模块用来做一些资源清理的操作，再好不过了。  </p>
<p>装饰器的方式：</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">atexit</span> <span class="kn">import</span> <span class="n">register</span>

<span class="nd">@register</span>
<span class="k">def</span> <span class="nf">_atexit</span><span class="p">():</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Done.&#39;</span><span class="p">)</span>

<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Start.&#39;</span><span class="p">)</span>
<span class="mi">1</span><span class="o">/</span><span class="mi">0</span>  <span class="c1"># 就算有异常，也不会有问题</span>
</code></pre></div>

<p>非装饰器的方式：</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">atexit</span> <span class="kn">import</span> <span class="n">register</span>

<span class="k">def</span> <span class="nf">befor_exit</span><span class="p">(</span><span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">):</span>
    <span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Done:&#39;</span><span class="p">,</span> <span class="n">arg1</span><span class="p">,</span> <span class="n">arg2</span><span class="p">)</span>

<span class="c1"># 第一个参数是注册的函数，后面是要传入的参数，位置参数和关键字参数都可以用</span>
<span class="n">register</span><span class="p">(</span><span class="n">befor_exit</span><span class="p">,</span> <span class="s1">&#39;value1&#39;</span><span class="p">,</span> <span class="n">arg2</span><span class="o">=</span><span class="s1">&#39;value2&#39;</span><span class="p">)</span>

<span class="nb">print</span><span class="p">(</span><span class="s1">&#39;Start.&#39;</span><span class="p">)</span>
<span class="mi">1</span><span class="o">/</span><span class="mi">0</span>
</code></pre></div>

<h5 id="zheng-ge-python-jiao-ben-dan-li-yun-xing"><a class="toclink" href="#zheng-ge-python-jiao-ben-dan-li-yun-xing">整个python脚本单例运行</a></h5>
<p>目前，自定的filelock模块，实现进程间的互斥锁。标准库的atexit模块实现注册终止函数。将这两个模块配合使用，将下面的代码放在脚本开头，就可以实现脚本的单例执行：</p>
<div class="highlight"><pre><span></span><code><span class="kn">from</span> <span class="nn">atexit</span> <span class="kn">import</span> <span class="n">register</span>
<span class="kn">from</span> <span class="nn">filelock</span> <span class="kn">import</span> <span class="n">FileLock</span>

<span class="n">register</span><span class="p">(</span><span class="n">FileLock</span><span class="p">(</span><span class="s1">&#39;flock.lock&#39;</span><span class="p">)</span><span class="o">.</span><span class="fm">__enter__</span><span class="p">()</span><span class="o">.</span><span class="n">release</span><span class="p">)</span>
</code></pre></div>

<p>上面的注册函数是一个链式调用。__enter__函数会执行并获取锁，然后返回对象自身。返回的对象的release方法才是最终注册的函数，注册的函数在解释器正常终止时会自动执行。  </p>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>